/******************************************************************************
 * Memember functions for data structure IoLocationMap
 ******************************************************************************/
/* Headers from vtrutil library */
#include <algorithm>

#include "vtr_assert.h"
#include "vtr_log.h"
#include "vtr_time.h"

/* Headers from openfpgautil library */
#include "io_location_map.h"
#include "openfpga_digest.h"

/* begin namespace openfpga */
namespace openfpga {

/**************************************************
 * Public Accessors
 *************************************************/
size_t IoLocationMap::io_index(const size_t& x, const size_t& y,
                               const size_t& z,
                               const std::string& io_port_name) const {
  std::array<size_t, 3> coord = {x, y, z};
  auto result = io_indices_.find(coord);
  if (result == io_indices_.end()) {
    return size_t(-1);
  }
  for (const BasicPort& candidate : result->second) {
    if (candidate.get_name() == io_port_name) {
      /* First found, first return. This may create bugs when FPGA architecture
       * are more flexible
       * FIXME: However, we should not have multiple I/O in the same name,
       * mapped to the same coordindate For example, PAD[0] -> (1, 0, 1) and
       * PAD[1] -> (1, 0, 1)
       */
      return candidate.get_lsb();
    }
  }
  return size_t(-1);
}

size_t IoLocationMap::io_x(const BasicPort& io_port) const {
  for (auto pair : io_indices_) {
    for (const BasicPort& candidate : pair.second) {
      if (candidate == io_port) {
        return pair.first[0];
      }
    }
  }
  return size_t(-1);
}

size_t IoLocationMap::io_y(const BasicPort& io_port) const {
  for (auto pair : io_indices_) {
    for (const BasicPort& candidate : pair.second) {
      if (candidate == io_port) {
        return pair.first[1];
      }
    }
  }
  return size_t(-1);
}

size_t IoLocationMap::io_z(const BasicPort& io_port) const {
  for (auto pair : io_indices_) {
    for (const BasicPort& candidate : pair.second) {
      if (candidate == io_port) {
        return pair.first[2];
      }
    }
  }
  return size_t(-1);
}

void IoLocationMap::set_io_index(const size_t& x, const size_t& y,
                                 const size_t& z,
                                 const std::string& io_port_name,
                                 const size_t& io_index) {
  std::array<size_t, 3> coord = {x, y, z};
  BasicPort port_to_add(io_port_name, io_index, io_index);
  auto result = io_indices_.find(coord);
  if (result != io_indices_.end()) {
    if (io_indices_.at(coord).end() != std::find(io_indices_.at(coord).begin(),
                                                 io_indices_.at(coord).end(),
                                                 port_to_add)) {
      VTR_LOG_WARN(
        "Attempt to add duplicated io '%s[%lu]' to coordinate (%lu, %lu, %lu)! "
        "Skip to save memory\n",
        io_port_name.c_str(), io_index, x, y, z);
    }
  }

  io_indices_[coord].push_back(port_to_add);
}

int IoLocationMap::write_to_xml_file(const std::string& fname,
                                     const bool& include_time_stamp,
                                     const bool& verbose) const {
  std::string timer_message =
    std::string("Write fabric I/O information to an XML file '") + fname +
    std::string("'");

  std::string dir_path = format_dir_path(find_path_dir_name(fname));

  /* Create directories */
  create_directory(dir_path);

  /* Start time count */
  vtr::ScopedStartFinishTimer timer(timer_message);

  /* Use default name if user does not provide one */
  VTR_ASSERT(true != fname.empty());

  /* Create a file handler*/
  std::fstream fp;
  /* Open a file */
  fp.open(fname, std::fstream::out | std::fstream::trunc);

  /* Validate the file stream */
  check_file_stream(fname.c_str(), fp);

  int err_code = 0;

  /* Write XML head */
  fp << "<!--" << std::endl;
  fp << "\t- FPGA Fabric I/O Information" << std::endl;
  fp << "\t- Generated by OpenFPGA" << std::endl;

  auto end = std::chrono::system_clock::now();
  std::time_t end_time = std::chrono::system_clock::to_time_t(end);
  if (include_time_stamp) {
    fp << "\t- Date: " << std::ctime(&end_time);
  }

  fp << "-->" << std::endl;
  fp << std::endl;

  fp << "<io_coordinates>\n";

  size_t io_cnt = 0;

  /* Walk through the fabric I/O location map data structure */
  for (auto pair : io_indices_) {
    for (const BasicPort& port : pair.second) {
      fp << "\t"
         << "<io pad=\"" << port.get_name().c_str() << "[" << port.get_lsb()
         << "]\"";
      fp << " "
         << "x=\"" << pair.first[0] << "\"";
      fp << " "
         << "y=\"" << pair.first[1] << "\"";
      fp << " "
         << "z=\"" << pair.first[2] << "\"";
      fp << "/>";
      fp << "\n";
      io_cnt++;
    }
  }

  /* Print an end to the file here */
  fp << "</io_coordinates>\n";

  /* close a file */
  fp.close();

  VTR_LOGV(verbose, "Outputted %lu I/Os to XML file: %s\n", io_cnt,
           fname.c_str());

  return err_code;
}

} /* end namespace openfpga */
